# coding=utf-8

from flask import request
from requests.cookies import RequestsCookieJar
from datetime import timedelta, datetime
import uuid
from typing import Optional, List, Mapping

from app.cores.dictionaries import DISPATCHER_TYPE

# requests库会在发起请求时根据请求服务器的domain选择合适的cookie作为请求头中的cookie数据发出
#   可参考代码 requests.models.prepare_cookies()


class HTTPCookieManager:
    """管理自动化测试中请求头Cookie数据"""

    # cookie池，不同的会话放到不同的字典中，key为区分会话唯一编号放入cookie中
    CookiePool = {}

    def __init__(self, app=None):
        if app is not None:
            self.init_app(app)

    def init_app(self, app):
        app.http_cookie_manager = self
        app.after_request(self._update_cookie)

    def update_cookie_pool(self, http_cookie_manager_id: str = None, rcj: RequestsCookieJar = None, type: str = None):
        """
        :param http_cookie_manager_id: 标识当前会话cookie编号
        :param rcj: RequestsCookieJar对象，一般由应答头获得 response.cookies
        :param type: 需要更新到的cookie类型 single_case或build_case
        :return: None
        """
        if http_cookie_manager_id is None:
            try:
                http_cookie_manager_id = request.cookies.get(HTTPCookieManagerConfig.COOKIE_NAME)
            except KeyError:
                raise KeyError('在请求上下文cookies中未找到名为%s的cookie' % HTTPCookieManagerConfig.COOKIE_NAME)

        if rcj is None or type is None:
            self._add_empty_to_cookie_pool(http_cookie_manager_id)
        else:
            if http_cookie_manager_id not in __class__.CookiePool:
                self._add_empty_to_cookie_pool(http_cookie_manager_id)
            if len(rcj) == 0:  # 空的RequestsCookieJar
                return
            if type in [DISPATCHER_TYPE.DEBUG, DISPATCHER_TYPE.BUILD]:
                old_rcj = __class__.CookiePool[http_cookie_manager_id][type]
                if old_rcj is None:
                    __class__.CookiePool[http_cookie_manager_id][type] = rcj
                else:
                    self._update_request_cookie_jar(old_rcj=old_rcj, new_rcj=rcj)
            else:
                raise ValueError('不支持传入type值为%s，只支持type=single_case 或 type=build_case' % type)

    def _update_request_cookie_jar(self, old_rcj: RequestsCookieJar, new_rcj: RequestsCookieJar):
        """将新的rcj更新到老的rcj上"""
        old_rcj.update(other=new_rcj)

    def _add_empty_to_cookie_pool(self, http_cookie_manager_id: str):
        """
        为当前会话在cookie pool中添加一个新的，默认值为RequestsCookieJar()
        :param http_cookie_manager_id: 标识当前会话cookie编号
        :return: None
        """
        __class__.CookiePool.update({
            http_cookie_manager_id: {
                DISPATCHER_TYPE.DEBUG: RequestsCookieJar(),
                DISPATCHER_TYPE.BUILD: RequestsCookieJar(),
            }
        })

    def get_request_cookie_jar(self, type: str) -> Optional[RequestsCookieJar]:
        """
        获取当前会话中指定类型的cookie数据
        :param type: cookie类型 single_case或build_case
        :return: RequestsCookieJar
        """
        http_cookie_manager_id = request.cookies.get(HTTPCookieManagerConfig.COOKIE_NAME)
        if http_cookie_manager_id not in __class__.CookiePool:
            return
        return __class__.CookiePool[http_cookie_manager_id][type]

    def clear_and_reset(self, rcj: RequestsCookieJar, cookies: List[Mapping]):
        """
        清除并重置RequestsCookieJar
        :param rcj: 待重置的RequestsCookieJar，来自cookie pool中该会话cookie
        :param cookies: 新的cookie数据，是一个由字典组成的列表，每一个字典表示一个新的cookie
        :return: None
        """
        if rcj is None:
            rcj = RequestsCookieJar()
        rcj.clear()
        for cookie in cookies:
            rcj.set(**cookie)

    def _update_cookie(self, response):
        """注册到app.after_request, 当浏览器没有该cookie时，将会在应答头中带上该cookie"""
        # 判断当前浏览器是否保存有HTTPCookieManager ID的Cookie
        # 如果没有的话则会在该浏览器中新增一个HTTPCookieManager ID的Cookie，并将ID值保存在CookiePool中
        if HTTPCookieManagerConfig.COOKIE_NAME not in request.cookies:
            duration = HTTPCookieManagerConfig.COOKIE_DURATION
            if isinstance(duration, int):
                duration = timedelta(seconds=duration)
            try:
                expires = datetime.utcnow() + duration
            except TypeError:
                raise Exception('HTTPCookieManagerConfig.COOKIE_DURATION must be a ' +
                                'datetime.timedelta, instead got: {0}'.format(duration))
            uuid_ = str(uuid.uuid1())
            response.set_cookie(HTTPCookieManagerConfig.COOKIE_NAME,
                                value=uuid_,  # 唯一标识
                                expires=expires,
                                domain=None,
                                path='/',
                                secure=HTTPCookieManagerConfig.COOKIE_SECURE,
                                httponly=HTTPCookieManagerConfig.COOKIE_HTTPONLY)
            self._add_empty_to_cookie_pool(uuid_)
        else:
            pass
        return response


class HTTPCookieManagerConfig:
    # 标识HTTPCookieManager在当前浏览器客户端对应id的cookie
    COOKIE_NAME = 'http_cookie_manager_id'

    COOKIE_DURATION = timedelta(days=365)

    COOKIE_SECURE = None

    COOKIE_HTTPONLY = False
